package com.myzl.coal.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.myzl.coal.domain.RealTimeData;
import com.myzl.coal.mapper.RealTimeDataMapper;
import com.myzl.coal.mongo.component.MongoComponent;
import com.myzl.coal.mongo.domain.HistoryData;
import com.myzl.coal.service.IDataStatisticsService;
import com.myzl.common.constant.CacheKeyConstants;
import com.myzl.common.core.domain.entity.SysDictData;
import com.myzl.common.enums.DeviceCommandEnum;
import com.myzl.common.enums.LedScreenContent;
import com.myzl.common.enums.OperateModeEnum;
import com.myzl.common.utils.DictUtils;
import com.myzl.common.utils.SpecificDateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 数据统计
 * @author cew
 * @date 2023年11月26日23:02:52
 */
@Slf4j(topic = "admin-user")
@Service
public class IDataStatisticsServiceImpl implements IDataStatisticsService {

    @Autowired
    private MongoComponent mongoComponent;
    @Autowired
    private RealTimeDataMapper realTimeDataMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public List<Object> scaleDashboard(Integer deviceId) {
        List<Object> dataList = new ArrayList<>();
        StopWatch stopWatch = new StopWatch();
        ExecutorService executor = null;
        try {
            stopWatch.start();
            // 线程池
            executor = Executors.newFixedThreadPool(9);
            // 瞬时量 任务执行
            CompletableFuture<String> current = getCurrentFuture(executor,LedScreenContent.CURRENT,deviceId);
            // 带速 任务执行
            CompletableFuture<String> speed = getCurrentFuture(executor,LedScreenContent.SPEED,deviceId);
            // 早 任务执行
            CompletableFuture<String> morning = getClassFuture(executor,LedScreenContent.MORNING,deviceId);
            // 中 任务执行
            CompletableFuture<String> noon = getClassFuture(executor,LedScreenContent.NOON,deviceId);
            // 晚 任务执行
            CompletableFuture<String> night = getClassFuture(executor,LedScreenContent.NIGHT,deviceId);
            // 日 任务执行
            CompletableFuture<String> day = getMoreFuture(executor,LedScreenContent.DAY,deviceId);
            // 月 任务执行
            CompletableFuture<String> month = getMoreFuture(executor,LedScreenContent.MONTH,deviceId);
            // 年 任务执行
            CompletableFuture<String> year = getMoreFuture(executor,LedScreenContent.YEAR,deviceId);
            // 全部 任务执行
            CompletableFuture<String> all = getMoreFuture(executor,LedScreenContent.ALL,deviceId);

            dataList.add(Float.parseFloat(current.get()));
            dataList.add(Float.parseFloat(speed.get()));
            dataList.add(Float.parseFloat(morning.get()));
            dataList.add(Float.parseFloat(noon.get()));
            dataList.add(Float.parseFloat(night.get()));
            dataList.add(Float.parseFloat(day.get()));
            dataList.add(Double.parseDouble(month.get()));
            dataList.add(Double.parseDouble(year.get()));
            dataList.add(Double.parseDouble(all.get()));
            log.info("统计结束耗时:[{}]毫秒",  stopWatch.getTime());
            return dataList;
        } catch (Exception e){
           log.error("数据统计异常：",e);
        } finally {
            if (executor != null){
                executor.shutdown();
            }
            stopWatch.stop();
        }
        return dataList;
    }

    @Override
    public List<Object> scaleDashboard1(Integer deviceId) {
        List<Object> dataList = new ArrayList<>();
        StopWatch stopWatch = new StopWatch();
        ExecutorService executor = null;
        try{
            stopWatch.start();
            // 线程池
            executor = Executors.newFixedThreadPool(6);
            // 瞬时量 任务执行
            CompletableFuture<String> current = getCurrentFuture(executor,LedScreenContent.CURRENT,deviceId);
            // 带速 任务执行
            CompletableFuture<String> speed = getCurrentFuture(executor,LedScreenContent.SPEED,deviceId);
            // 当前班 任务执行
            CompletableFuture<String> currentClass = getAllFuture(executor,CacheKeyConstants.CURRENT_CLASS,deviceId);
            // 日 任务执行
            CompletableFuture<String> day = getAllFuture(executor,CacheKeyConstants.DAY,deviceId);
            // 月 任务执行
            CompletableFuture<String> month = getAllFuture(executor,CacheKeyConstants.MONTH,deviceId);
            // 年 任务执行
            CompletableFuture<String> year = getAllFuture(executor,CacheKeyConstants.YEAR,deviceId);

            dataList.add(Float.parseFloat(current.get()));
            dataList.add(Float.parseFloat(speed.get()));
            dataList.add(Double.parseDouble(currentClass.get()));
            dataList.add(Double.parseDouble(day.get()));
            dataList.add(Double.parseDouble(month.get()));
            dataList.add(Double.parseDouble(year.get()));
            log.info("统计结束耗时:[{}]毫秒",  stopWatch.getTime());
            return dataList;
        }catch (Exception e){
            log.error("数据统计异常：",e);
        } finally {
            if (executor != null) {
                executor.shutdown();
            }
            stopWatch.stop();
        }
        return dataList;
    }

    private CompletableFuture<String> getCurrentFuture(ExecutorService executor,LedScreenContent ledScreenContent,Integer deviceId){
       return CompletableFuture.supplyAsync(() -> getCurrent(ledScreenContent,deviceId),executor);
    }

    private CompletableFuture<String> getClassFuture(ExecutorService executor,LedScreenContent ledScreenContent,Integer deviceId){
        return CompletableFuture.supplyAsync(() -> getClass(ledScreenContent,deviceId),executor);
    }

    private CompletableFuture<String> getMoreFuture(ExecutorService executor,LedScreenContent ledScreenContent,Integer deviceId){
        return CompletableFuture.supplyAsync(() -> getMore(ledScreenContent,deviceId),executor);
    }

    private CompletableFuture<String> getAllFuture(ExecutorService executor,String key,Integer deviceId){
        return CompletableFuture.supplyAsync(() -> getAll(key,deviceId),executor);
    }

    private String getCurrent(LedScreenContent ledScreenContent, Integer deviceId){
        String result = "";
        List<Integer> dictValues = buildDictValues();
        List<RealTimeData> currentRealTimeDataList = realTimeDataMapper.selectList(new LambdaQueryWrapper<RealTimeData>()
                .eq(RealTimeData::getDeviceId, deviceId)
                .in(RealTimeData::getDictValue, dictValues));
        Map<Integer,String> currentRealTimeMap = currentRealTimeDataList.stream()
                .collect(Collectors.toMap(RealTimeData::getDictValue,RealTimeData::getDataValue));
        // 当前带速模式
        String operateMode = currentRealTimeMap.get(DeviceCommandEnum.OPERATE_MODE.getCode());
        operateMode = ObjectUtils.isEmpty(operateMode) ? OperateModeEnum.UNIFORM_SPEED.getCode() : operateMode;
        if (OperateModeEnum.UNIFORM_SPEED.getCode().equals(operateMode)){
            // 当前流量
            switch (ledScreenContent) {
                case CURRENT:
                    result = toResult(currentRealTimeMap.get(DeviceCommandEnum.CURRENT_FLOW.getCode()));
                    break;
                case SPEED:
                    result = toResult(currentRealTimeMap.get(DeviceCommandEnum.UNIFORM_SPEED.getCode()));
                    break;
                default:
            }
        }else {
            // 当前流量
            switch (ledScreenContent) {
                case CURRENT:
                    List<HistoryData> flowData = getTimeSlotData(deviceId, DeviceCommandEnum.CUMULATIVE_FLOW.getCode(), 2);
                    BigDecimal currentData = getCurrentData(flowData);
                    result = toResult(currentData.toString());
                    break;
                case SPEED:
                    result = toResult(currentRealTimeMap.get(DeviceCommandEnum.VARIABLE_SPEED.getCode()));
                    break;
                default:
            }
        }
        return result;
    }

    private String getClass(LedScreenContent ledScreenContent,Integer deviceId) {
        String result= "";
        String date = DateUtil.today();
        List<SysDictData> shiftTimeList = DictUtils.getDictCache("everyday_shift_time");
        for (SysDictData data : shiftTimeList) {
            if (ledScreenContent.getInfo().equals(data.getDictLabel())) {
                String[] timeArray = data.getDictValue().split(",");
                DateTime startTime,endTime;
                if (timeArray[0].startsWith("-")) {
                    startTime = SpecificDateUtil.getMsCurrentClassTime(date,timeArray[0]);
                } else {
                    startTime = SpecificDateUtil.getMsNextClassTime(date,timeArray[0]);
                }
                if (timeArray[1].startsWith("-")) {
                    endTime = SpecificDateUtil.getMsCurrentClassTime(date,timeArray[1]);
                } else {
                    endTime = SpecificDateUtil.getMsNextClassTime(date,timeArray[1]);
                }
                List<HistoryData> currentClassFlowData = getMsTimeSlotList(deviceId, DeviceCommandEnum.CUMULATIVE_FLOW.getCode(), startTime, endTime);
                result = getStatisticsData(currentClassFlowData);
                result = toResult(result);
                currentClassFlowData.clear();
                break;
            }
        }
        return result;
    }

    private String getMore(LedScreenContent ledScreenContent,Integer deviceId){
        String result = "";
        String date = DateUtil.today();
        DateTime current = null;
        switch (ledScreenContent) {
            case DAY:
                current = SpecificDateUtil.getMsCurrentDay(date);
                break;
            case MONTH:
                current = SpecificDateUtil.getMsCurrentMonth(date);
                break;
            case YEAR:
                current = SpecificDateUtil.getMsCurrentYear(date);
            case ALL:
                break;
            default:
        }
        List<HistoryData> flowList = getMsTimeSlotList(deviceId, DeviceCommandEnum.CUMULATIVE_FLOW.getCode(), current);
        result = getStatisticsData(flowList);
        result = toResult(result);
        return result;
    }

    private String getAll(String key,Integer deviceId){
        Map<Object, Object> timeStart = redisTemplate.opsForHash().entries(CacheKeyConstants.KEY);
        Object timeValue = timeStart.get(key);
        DateTime startTime = new DateTime(0);
        if (timeValue != null){
            startTime = new DateTime(Long.parseLong(timeValue.toString()));
        }
        List<HistoryData> flowList = getMsTimeSlotList(deviceId, DeviceCommandEnum.CUMULATIVE_FLOW.getCode(), startTime);
        String flow = getStatisticsData(flowList);
        flow = toResult(flow);
        return flow;
    }

    private List<Integer> buildDictValues() {
        List<Integer> dictValues = new ArrayList<>();
        dictValues.add(DeviceCommandEnum.CURRENT_FLOW.getCode());
        dictValues.add(DeviceCommandEnum.CUMULATIVE_FLOW.getCode());
        dictValues.add(DeviceCommandEnum.OPERATE_MODE.getCode());
        dictValues.add(DeviceCommandEnum.VARIABLE_SPEED.getCode());
        dictValues.add(DeviceCommandEnum.UNIFORM_SPEED.getCode());
        return dictValues;
    }

    private List<HistoryData> getTimeSlotData(Integer deviceId, int dictValue, int limit){
        Criteria criteria = new Criteria();
        criteria.andOperator(
                Criteria.where("deviceId").is(deviceId),
                Criteria.where("dictValue").is(dictValue)
        );
        Sort ascSort = Sort.by(Sort.Order.desc("createTime"));
        if (limit > 0){
            return mongoComponent.selectList(criteria, limit, ascSort, HistoryData.class);
        } else {
            return mongoComponent.selectList(criteria, ascSort, HistoryData.class);
        }
    }

    private List<HistoryData> getMsTimeSlotList(Integer deviceId, int dictValue, DateTime currentDay){
        Criteria criteria = new Criteria();
        if (ObjUtil.isNotEmpty(currentDay)){
            criteria.andOperator(
                    Criteria.where("deviceId").is(deviceId),
                    Criteria.where("dictValue").is(dictValue),
                    Criteria.where("createTime").gte(currentDay)
            );
        } else {
            criteria.andOperator(
                    Criteria.where("deviceId").is(deviceId),
                    Criteria.where("dictValue").is(dictValue)
            );
        }
        return getTimeHistoryData(criteria);
    }

    private List<HistoryData> getMsTimeSlotList(Integer deviceId, int dictValue, DateTime startTime,DateTime endTime){
        Criteria criteria = new Criteria();
        criteria.andOperator(
                Criteria.where("deviceId").is(deviceId),
                Criteria.where("dictValue").is(dictValue),
                Criteria.where("createTime").gte(startTime),
                Criteria.where("createTime").lte(endTime)
        );
        return getTimeHistoryData(criteria);
    }

    private List<HistoryData> getTimeHistoryData(Criteria criteria) {
        List<HistoryData> result = new ArrayList<>();
        Sort ascSort = Sort.by(Sort.Order.asc("createTime"));
        Sort descSort = Sort.by(Sort.Order.desc("createTime"));
        List<HistoryData> startList = mongoComponent.selectList(criteria, 1, ascSort, HistoryData.class);
        if (CollUtil.isNotEmpty(startList)){
            result.add(startList.get(0));
        }
        List<HistoryData> endList = mongoComponent.selectList(criteria, 1, descSort, HistoryData.class);
        if (CollUtil.isNotEmpty(endList)){
            result.add(endList.get(0));
        }
        return result;
    }

    private String getStatisticsData(List<HistoryData> dataList){
        BigDecimal result = new BigDecimal("0.00");
        BigDecimal zero = new BigDecimal("0.00");
        if (CollUtil.isNotEmpty(dataList)) {
            HistoryData maxhistoryData = dataList.get(dataList.size() - 1);
            HistoryData minhistoryData = dataList.get(0);
            BigDecimal max = new BigDecimal(maxhistoryData.getDataValue());
            BigDecimal min = new BigDecimal(minhistoryData.getDataValue());
            result = max.subtract(min);
            result = result.compareTo(zero) < 0 ? zero : result;
        }
        return result.toString();
    }

    private BigDecimal getCurrentData(List<HistoryData> flowData) {
        //变速计算两条时间间隔计算数据
        BigDecimal currentData = new BigDecimal("0.00");
        if (flowData.size() == 2){
            Date lastTime = flowData.get(0).getCreateTime();
            Date firstTime = flowData.get(1).getCreateTime();
            long betweenTime  = DateUtil.between(lastTime, firstTime , DateUnit.MS);
            BigDecimal intervalTime = new BigDecimal(betweenTime).divide(new BigDecimal(1000),2, RoundingMode.HALF_UP);
            BigDecimal lastData = new BigDecimal(flowData.get(0).getDataValue());
            BigDecimal firstData = new BigDecimal(flowData.get(1).getDataValue());
            if (intervalTime.compareTo(new BigDecimal("0")) > 0 && lastData.compareTo(firstData) >= 0){
                currentData = lastData.subtract(firstData);
                currentData = currentData.divide(intervalTime,5, RoundingMode.HALF_UP).multiply(new BigDecimal("3600"));
            }
        }
        return currentData;
    }

    /**
     * 保留两位小数
     * @param data 传入数据
     * @return 结果
     */
    public static String toResult(String data){
        BigDecimal dataBigDecimal = new BigDecimal(data).setScale(2, RoundingMode.HALF_UP);
        return dataBigDecimal.toString();
    }
}
